/*
 * Copyright (C) 2009 Rafael Fernandes, Jeff Sharkey, http://jsharkey.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.phonebooksharing.android.services;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
import android.widget.RemoteViews;

import com.phonebooksharing.android.async.DashboardUpdaterTask;
import com.phonebooksharing.android.provider.PhonebookSharing.DesktopMessage;
import com.phonebooksharing.android.provider.PhonebookSharing.DesktopWidgets;
import com.phonebooksharing.android.util.CloseableUtil;
import com.phonebooksharing.android.util.Global;
import com.phonebooksharing.android.util.NetworkHandlerTask;
import com.phonebooksharing.android.widgets.desktop.TextMessageAppWidgetProvider;
import com.phonebooksharing.android.widgets.desktop.VoiceMessageAppWidgetProvider;
import com.phonebooksharing.dto.dashboard.DashboardReaderItem;
import com.phonebooksharing.dto.friend.ContactItem;
import com.phonebooksharing.dto.msg.TextMessageItem;

/**
 * Background service to build any requested widget updates. Uses a single
 * background thread to walk through an update queue, querying
 * {@link WebserviceHelper} as needed to fill database. Also handles scheduling
 * of future updates, usually in 6-hour increments.
 * 
 * @author Jeff Sharkey, http://jsharkey.org/ (Examples)
 * @author Rafael Fernandes (Adapted based upon Sharkey's examples)
 */
public class DesktopMessageUpdaterService extends Service {
    private static final String TAG = "UpdateService";
    
    private static final String[] PROJECTION_APPWIDGETS = new String[] {
        DesktopWidgets.UPDATE_INTERVAL,
        DesktopWidgets.IS_CONFIGURED,
    };

    private static final long DEFAULT_UPDATE_INTERVAL = 1;
    
    /**
     * When rounding updates to the nearest-top-of-hour, trigger the update
     * slightly early by this amount. This makes sure that we're already updated
     * when the user's 6AM alarm clock goes off.
     */
    private static final long UPDATE_TRIGGER_EARLY = 10 * DateUtils.MINUTE_IN_MILLIS;

    /**
     * If we calculated an update too quickly in the future, wait this interval
     * and try rescheduling.
     */
    private static final long UPDATE_THROTTLE = 14 * DateUtils.MINUTE_IN_MILLIS;

    /**
     * Specific {@link Intent#setAction(String)} used when performing a full
     * update of all widgets, usually when an update alarm goes off.
     */
    public static final String ACTION_UPDATE_ALL = "com.phonebooksharing.android.DESKTOP_MESSAGE_WIDGETS_UPDATE";

    /**
     * Lock used when maintaining queue of requested updates.
     */
    private static Object sLock = new Object();

    /**
     * Flag if there is an update thread already running. We only launch a new
     * thread if one isn't already running.
     */
    private static boolean sThreadRunning = false;

    /**
     * Internal queue of requested widget updates. You <b>must</b> access
     * through {@link #requestUpdate(int[])} or {@link #getNextUpdate()} to make
     * sure your access is correctly synchronized.
     */
    private static Queue<Integer> sAppWidgetIds = new LinkedList<Integer>();

    /**
     * Request updates for the given widgets. Will only queue them up, you are
     * still responsible for starting a processing thread if needed, usually by
     * starting the parent service.
     */
    public static void requestUpdate(int[] appWidgetIds) {
        synchronized (sLock) {
            for (int appWidgetId : appWidgetIds) {
                sAppWidgetIds.add(appWidgetId);
            }
        }
    }

    /**
     * Peek if we have more updates to perform. This method is special because
     * it assumes you're calling from the update thread, and that you will
     * terminate if no updates remain. (It atomically resets
     * {@link #sThreadRunning} when none remain to prevent race conditions.)
     */
    private static boolean hasMoreUpdates() {
        synchronized (sLock) {
            boolean hasMore = !sAppWidgetIds.isEmpty();
            if (!hasMore) {
                sThreadRunning = false;
            }
            return hasMore;
        }
    }

    /**
     * Poll the next widget update in the queue.
     */
    private static int getNextUpdate() {
        synchronized (sLock) {
            if (sAppWidgetIds.peek() == null) {
                return AppWidgetManager.INVALID_APPWIDGET_ID;
            } else {
                return sAppWidgetIds.poll();
            }
        }
    }

    /**
     * Start this service, creating a background processing thread, if not
     * already running. If started with {@link #ACTION_UPDATE_ALL}, will
     * automatically add all widgets to the requested update queue.
     */
    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);

        // If requested, trigger update of all widgets
        if (ACTION_UPDATE_ALL.equals(intent.getAction())) {
            Log.d(TAG, "Requested UPDATE_ALL action");
            AppWidgetManager manager = AppWidgetManager.getInstance(this);
            requestUpdate(manager.getAppWidgetIds(new ComponentName(this, TextMessageAppWidgetProvider.class)));
            requestUpdate(manager.getAppWidgetIds(new ComponentName(this, VoiceMessageAppWidgetProvider.class)));
        }

        // Only start processing thread if not already running
        synchronized (sLock) {
            if (!sThreadRunning) {
                sThreadRunning = true;
                new DashUpdaterTask(this).execute(this);
            }
        }
    }
    
    private MessageTask task;
    
    private class DashUpdaterTask extends DashboardUpdaterTask {
        Context cx;
        public DashUpdaterTask(Context cx) {
            this.cx = cx;
            Global.loadConfig(cx);
        }

        @Override
        public void onPostExecute(Void void1) {
            super.onPostExecute(void1);
            task = new MessageTask();
            task.execute(cx);
        }
    }
    
    private class MessageTask extends NetworkHandlerTask<Context, Void, Void> {
        @SuppressWarnings("unchecked")
        @Override
        protected Void doInBackground(Context... params) {
            Context cx = params[0];
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(cx);
            ContentResolver cr = getContentResolver();
            long updateInterval = 0;
            boolean isConfig = false;
            
            while (hasMoreUpdates()) {
                int appWidgetId = getNextUpdate();
                Uri appWidgetUri = ContentUris.withAppendedId(DesktopWidgets.CONTENT_URI, appWidgetId);

                Cursor cursor = null;
                try {
                    cursor = cr.query(appWidgetUri, PROJECTION_APPWIDGETS, null, null, null);
                    if (cursor != null && cursor.moveToFirst()) {
                        //0 is update interval
                        updateInterval = cursor.getLong(0);
                        
                        isConfig = cursor.getInt(1) > 0;
                    }
                } finally {
                    CloseableUtil.safeClose(cursor);
                    cursor = null;
                }
                
                if(!isConfig) continue;

                try {
                    Global.loadConfig(cx);//make sure authItem is loaded properly
                    final String url = String.format(Global.DASHBOARD_SERVLET_URL, "m" /*ac*/, "1" /*q*/, "1" /*p*/, "3" /*ps*/);//q=1 means text only
                    ArrayList<DashboardReaderItem<ContactItem>> list = (ArrayList<DashboardReaderItem<ContactItem>>)doNetworkPost(url, Global.authItem);
                    
                    //clean up first
                    cr.delete(DesktopMessage.CONTENT_URI, DesktopMessage.TYPE + "=" + DesktopMessage.MSG_TYPE_TEXT, null);
                    
                    ContentValues cv;
                    ContactItem ci;
                    for (DashboardReaderItem<ContactItem> dri : list) {
                        cv = new ContentValues();
                        cv.put(DesktopMessage.WHEN, dri.getWhen());
                        ci = dri.getItem();
                        if(ci instanceof TextMessageItem) {
                            TextMessageItem ti = (TextMessageItem)ci;
                            cv.put(DesktopMessage.CONTACT_NAME, ti.name);
                            cv.put(DesktopMessage.TYPE, DesktopMessage.MSG_TYPE_TEXT);
                            cv.put(DesktopMessage.TITLE, ti.title);
                        } else {
                            //TODO:
                            cv = null; // not inserting
                        }

                        if(cv != null) {
                            cr.insert(DesktopMessage.CONTENT_URI, cv);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                
                // Process this update through the correct provider
                AppWidgetProviderInfo info = appWidgetManager.getAppWidgetInfo(appWidgetId);
                String providerName = info.provider.getClassName();
                RemoteViews updateViews = null;

                if (providerName.equals(TextMessageAppWidgetProvider.class.getName())) {
                    updateViews = TextMessageAppWidgetProvider.buildUpdate(cx, appWidgetUri);
                } else if (providerName.equals(VoiceMessageAppWidgetProvider.class.getName())) {
                    updateViews = VoiceMessageAppWidgetProvider.buildUpdate(cx, appWidgetUri);
                }

                // Push this update to surface
                if (updateViews != null) {
                    appWidgetManager.updateAppWidget(appWidgetId, updateViews);
                }

                if(updateInterval <= 0) {
                    updateInterval = DEFAULT_UPDATE_INTERVAL;
                }
                updateInterval = (updateInterval * DateUtils.MINUTE_IN_MILLIS);

                // Schedule next update alarm, usually just before a 6-hour block. This
                // triggers updates at roughly 5:50AM, 11:50AM, 5:50PM, and 11:50PM.
                Time time = new Time();
                time.set(System.currentTimeMillis() + updateInterval + UPDATE_TRIGGER_EARLY);
//                time.hour = 0;
//                time.minute -= (time.minute % 6);
//                time.second = 0;

                long nextUpdate = time.toMillis(false) - UPDATE_TRIGGER_EARLY;
                long nowMillis = System.currentTimeMillis();

                // Throttle our updates just in case the math went funky
                if (nextUpdate - nowMillis < UPDATE_THROTTLE) {
                    Log.d(TAG, "Calculated next update too early, throttling for a few minutes");
                    nextUpdate = nowMillis + UPDATE_THROTTLE;
                }

                long deltaMinutes = (nextUpdate - nowMillis) / DateUtils.MINUTE_IN_MILLIS;
                Log.d(TAG, "Requesting next update at " + nextUpdate + ", in " + deltaMinutes + " min");

                Intent updateIntent = new Intent(ACTION_UPDATE_ALL);
                updateIntent.setClass(cx, DesktopMessageUpdaterService.class);

                PendingIntent pendingIntent = PendingIntent.getService(cx, 0, updateIntent, 0);

                // Schedule alarm, and force the device awake for this update
                AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
                alarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            // No updates remaining, so stop service
            stopSelf();
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
